/*
 * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.naming;

import java.util.Locale;
import java.util.Vector;
import java.util.Enumeration;
import java.util.Properties;
import java.util.NoSuchElementException;

/**
 * The implementation class for CompoundName and CompositeName.
 * This class is package private.
 *
 * @author Rosanna Lee
 * @author Scott Seligman
 * @author Aravindan Ranganathan
 * @since 1.3
 */

class NameImpl {

  private static final byte LEFT_TO_RIGHT = 1;
  private static final byte RIGHT_TO_LEFT = 2;
  private static final byte FLAT = 0;

  private Vector<String> components;

  private byte syntaxDirection = LEFT_TO_RIGHT;
  private String syntaxSeparator = "/";
  private String syntaxSeparator2 = null;
  private boolean syntaxCaseInsensitive = false;
  private boolean syntaxTrimBlanks = false;
  private String syntaxEscape = "\\";
  private String syntaxBeginQuote1 = "\"";
  private String syntaxEndQuote1 = "\"";
  private String syntaxBeginQuote2 = "'";
  private String syntaxEndQuote2 = "'";
  private String syntaxAvaSeparator = null;
  private String syntaxTypevalSeparator = null;

  // escapingStyle gives the method used at creation time for
  // quoting or escaping characters in the name.  It is set to the
  // first style of quote or escape encountered if and when the name
  // is parsed.
  private static final int STYLE_NONE = 0;
  private static final int STYLE_QUOTE1 = 1;
  private static final int STYLE_QUOTE2 = 2;
  private static final int STYLE_ESCAPE = 3;
  private int escapingStyle = STYLE_NONE;

  // Returns true if "match" is not null, and n contains "match" at
  // position i.
  private final boolean isA(String n, int i, String match) {
    return (match != null && n.startsWith(match, i));
  }

  private final boolean isMeta(String n, int i) {
    return (isA(n, i, syntaxEscape) ||
        isA(n, i, syntaxBeginQuote1) ||
        isA(n, i, syntaxBeginQuote2) ||
        isSeparator(n, i));
  }

  private final boolean isSeparator(String n, int i) {
    return (isA(n, i, syntaxSeparator) ||
        isA(n, i, syntaxSeparator2));
  }

  private final int skipSeparator(String name, int i) {
    if (isA(name, i, syntaxSeparator)) {
      i += syntaxSeparator.length();
    } else if (isA(name, i, syntaxSeparator2)) {
      i += syntaxSeparator2.length();
    }
    return (i);
  }

  private final int extractComp(String name, int i, int len, Vector<String> comps)
      throws InvalidNameException {
    String beginQuote;
    String endQuote;
    boolean start = true;
    boolean one = false;
    StringBuffer answer = new StringBuffer(len);

    while (i < len) {
      // handle quoted strings
      if (start && ((one = isA(name, i, syntaxBeginQuote1)) ||
          isA(name, i, syntaxBeginQuote2))) {

        // record choice of quote chars being used
        beginQuote = one ? syntaxBeginQuote1 : syntaxBeginQuote2;
        endQuote = one ? syntaxEndQuote1 : syntaxEndQuote2;
        if (escapingStyle == STYLE_NONE) {
          escapingStyle = one ? STYLE_QUOTE1 : STYLE_QUOTE2;
        }

        // consume string until matching quote
        for (i += beginQuote.length();
            ((i < len) && !name.startsWith(endQuote, i));
            i++) {
          // skip escape character if it is escaping ending quote
          // otherwise leave as is.
          if (isA(name, i, syntaxEscape) &&
              isA(name, i + syntaxEscape.length(), endQuote)) {
            i += syntaxEscape.length();
          }
          answer.append(name.charAt(i));  // copy char
        }

        // no ending quote found
        if (i >= len) {
          throw
              new InvalidNameException(name + ": no close quote");
        }
//                      new Exception("no close quote");

        i += endQuote.length();

        // verify that end-quote occurs at separator or end of string
        if (i == len || isSeparator(name, i)) {
          break;
        }
//              throw (new Exception(
        throw (new InvalidNameException(name +
            ": close quote appears before end of component"));

      } else if (isSeparator(name, i)) {
        break;

      } else if (isA(name, i, syntaxEscape)) {
        if (isMeta(name, i + syntaxEscape.length())) {
          // if escape precedes meta, consume escape and let
          // meta through
          i += syntaxEscape.length();
          if (escapingStyle == STYLE_NONE) {
            escapingStyle = STYLE_ESCAPE;
          }
        } else if (i + syntaxEscape.length() >= len) {
          throw (new InvalidNameException(name +
              ": unescaped " + syntaxEscape + " at end of component"));
        }
      } else if (isA(name, i, syntaxTypevalSeparator) &&
          ((one = isA(name, i + syntaxTypevalSeparator.length(), syntaxBeginQuote1)) ||
              isA(name, i + syntaxTypevalSeparator.length(), syntaxBeginQuote2))) {
        // Handle quote occurring after typeval separator
        beginQuote = one ? syntaxBeginQuote1 : syntaxBeginQuote2;
        endQuote = one ? syntaxEndQuote1 : syntaxEndQuote2;

        i += syntaxTypevalSeparator.length();
        answer.append(syntaxTypevalSeparator + beginQuote); // add back

        // consume string until matching quote
        for (i += beginQuote.length();
            ((i < len) && !name.startsWith(endQuote, i));
            i++) {
          // skip escape character if it is escaping ending quote
          // otherwise leave as is.
          if (isA(name, i, syntaxEscape) &&
              isA(name, i + syntaxEscape.length(), endQuote)) {
            i += syntaxEscape.length();
          }
          answer.append(name.charAt(i));  // copy char
        }

        // no ending quote found
        if (i >= len) {
          throw
              new InvalidNameException(name + ": typeval no close quote");
        }

        i += endQuote.length();
        answer.append(endQuote); // add back

        // verify that end-quote occurs at separator or end of string
        if (i == len || isSeparator(name, i)) {
          break;
        }
        throw (new InvalidNameException(name.substring(i) +
            ": typeval close quote appears before end of component"));
      }

      answer.append(name.charAt(i++));
      start = false;
    }

    if (syntaxDirection == RIGHT_TO_LEFT) {
      comps.insertElementAt(answer.toString(), 0);
    } else {
      comps.addElement(answer.toString());
    }
    return i;
  }

  private static boolean getBoolean(Properties p, String name) {
    return toBoolean(p.getProperty(name));
  }

  private static boolean toBoolean(String name) {
    return ((name != null) &&
        name.toLowerCase(Locale.ENGLISH).equals("true"));
  }

  private final void recordNamingConvention(Properties p) {
    String syntaxDirectionStr =
        p.getProperty("jndi.syntax.direction", "flat");
    if (syntaxDirectionStr.equals("left_to_right")) {
      syntaxDirection = LEFT_TO_RIGHT;
    } else if (syntaxDirectionStr.equals("right_to_left")) {
      syntaxDirection = RIGHT_TO_LEFT;
    } else if (syntaxDirectionStr.equals("flat")) {
      syntaxDirection = FLAT;
    } else {
      throw new IllegalArgumentException(syntaxDirectionStr +
          "is not a valid value for the jndi.syntax.direction property");
    }

    if (syntaxDirection != FLAT) {
      syntaxSeparator = p.getProperty("jndi.syntax.separator");
      syntaxSeparator2 = p.getProperty("jndi.syntax.separator2");
      if (syntaxSeparator == null) {
        throw new IllegalArgumentException(
            "jndi.syntax.separator property required for non-flat syntax");
      }
    } else {
      syntaxSeparator = null;
    }
    syntaxEscape = p.getProperty("jndi.syntax.escape");

    syntaxCaseInsensitive = getBoolean(p, "jndi.syntax.ignorecase");
    syntaxTrimBlanks = getBoolean(p, "jndi.syntax.trimblanks");

    syntaxBeginQuote1 = p.getProperty("jndi.syntax.beginquote");
    syntaxEndQuote1 = p.getProperty("jndi.syntax.endquote");
    if (syntaxEndQuote1 == null && syntaxBeginQuote1 != null) {
      syntaxEndQuote1 = syntaxBeginQuote1;
    } else if (syntaxBeginQuote1 == null && syntaxEndQuote1 != null) {
      syntaxBeginQuote1 = syntaxEndQuote1;
    }
    syntaxBeginQuote2 = p.getProperty("jndi.syntax.beginquote2");
    syntaxEndQuote2 = p.getProperty("jndi.syntax.endquote2");
    if (syntaxEndQuote2 == null && syntaxBeginQuote2 != null) {
      syntaxEndQuote2 = syntaxBeginQuote2;
    } else if (syntaxBeginQuote2 == null && syntaxEndQuote2 != null) {
      syntaxBeginQuote2 = syntaxEndQuote2;
    }

    syntaxAvaSeparator = p.getProperty("jndi.syntax.separator.ava");
    syntaxTypevalSeparator =
        p.getProperty("jndi.syntax.separator.typeval");
  }

  NameImpl(Properties syntax) {
    if (syntax != null) {
      recordNamingConvention(syntax);
    }
    components = new Vector<>();
  }

  NameImpl(Properties syntax, String n) throws InvalidNameException {
    this(syntax);

    boolean rToL = (syntaxDirection == RIGHT_TO_LEFT);
    boolean compsAllEmpty = true;
    int len = n.length();

    for (int i = 0; i < len; ) {
      i = extractComp(n, i, len, components);

      String comp = rToL
          ? components.firstElement()
          : components.lastElement();
      if (comp.length() >= 1) {
        compsAllEmpty = false;
      }

      if (i < len) {
        i = skipSeparator(n, i);
        if ((i == len) && !compsAllEmpty) {
          // Trailing separator found.  Add an empty component.
          if (rToL) {
            components.insertElementAt("", 0);
          } else {
            components.addElement("");
          }
        }
      }
    }
  }

  NameImpl(Properties syntax, Enumeration<String> comps) {
    this(syntax);

    // %% comps could shrink in the middle.
    while (comps.hasMoreElements()) {
      components.addElement(comps.nextElement());
    }
  }

  /*
      // Determines whether this component needs any escaping.
      private final boolean escapingNeeded(String comp) {
          int len = comp.length();
          for (int i = 0; i < len; i++) {
              if (i == 0) {
                  if (isA(comp, 0, syntaxBeginQuote1) ||
                      isA(comp, 0, syntaxBeginQuote2)) {
                      return (true);
                  }
              }
              if (isSeparator(comp, i)) {
                  return (true);
              }
              if (isA(comp, i, syntaxEscape)) {
                  i += syntaxEscape.length();
                  if (i >= len || isMeta(comp, i)) {
                      return (true);
                  }
              }
          }
          return (false);
      }
  */
  private final String stringifyComp(String comp) {
    int len = comp.length();
    boolean escapeSeparator = false, escapeSeparator2 = false;
    String beginQuote = null, endQuote = null;
    StringBuffer strbuf = new StringBuffer(len);

    // determine whether there are any separators; if so escape
    // or quote them
    if (syntaxSeparator != null &&
        comp.indexOf(syntaxSeparator) >= 0) {
      if (syntaxBeginQuote1 != null) {
        beginQuote = syntaxBeginQuote1;
        endQuote = syntaxEndQuote1;
      } else if (syntaxBeginQuote2 != null) {
        beginQuote = syntaxBeginQuote2;
        endQuote = syntaxEndQuote2;
      } else if (syntaxEscape != null) {
        escapeSeparator = true;
      }
    }
    if (syntaxSeparator2 != null &&
        comp.indexOf(syntaxSeparator2) >= 0) {
      if (syntaxBeginQuote1 != null) {
        if (beginQuote == null) {
          beginQuote = syntaxBeginQuote1;
          endQuote = syntaxEndQuote1;
        }
      } else if (syntaxBeginQuote2 != null) {
        if (beginQuote == null) {
          beginQuote = syntaxBeginQuote2;
          endQuote = syntaxEndQuote2;
        }
      } else if (syntaxEscape != null) {
        escapeSeparator2 = true;
      }
    }

    // if quoting component,
    if (beginQuote != null) {

      // start string off with opening quote
      strbuf = strbuf.append(beginQuote);

      // component is being quoted, so we only need to worry about
      // escaping end quotes that occur in component
      for (int i = 0; i < len; ) {
        if (comp.startsWith(endQuote, i)) {
          // end-quotes must be escaped when inside a quoted string
          strbuf.append(syntaxEscape).append(endQuote);
          i += endQuote.length();
        } else {
          // no special treatment required
          strbuf.append(comp.charAt(i++));
        }
      }

      // end with closing quote
      strbuf.append(endQuote);

    } else {

      // When component is not quoted, add escape for:
      // 1. leading quote
      // 2. an escape preceding any meta char
      // 3. an escape at the end of a component
      // 4. separator

      // go through characters in component and escape where necessary
      boolean start = true;
      for (int i = 0; i < len; ) {
        // leading quote must be escaped
        if (start && isA(comp, i, syntaxBeginQuote1)) {
          strbuf.append(syntaxEscape).append(syntaxBeginQuote1);
          i += syntaxBeginQuote1.length();
        } else if (start && isA(comp, i, syntaxBeginQuote2)) {
          strbuf.append(syntaxEscape).append(syntaxBeginQuote2);
          i += syntaxBeginQuote2.length();
        } else

          // Escape an escape preceding meta characters, or at end.
          // Other escapes pass through.
          if (isA(comp, i, syntaxEscape)) {
            if (i + syntaxEscape.length() >= len) {
              // escape an ending escape
              strbuf.append(syntaxEscape);
            } else if (isMeta(comp, i + syntaxEscape.length())) {
              // escape meta strings
              strbuf.append(syntaxEscape);
            }
            strbuf.append(syntaxEscape);
            i += syntaxEscape.length();
          } else

            // escape unescaped separator
            if (escapeSeparator && comp.startsWith(syntaxSeparator, i)) {
              // escape separator
              strbuf.append(syntaxEscape).append(syntaxSeparator);
              i += syntaxSeparator.length();
            } else if (escapeSeparator2 &&
                comp.startsWith(syntaxSeparator2, i)) {
              // escape separator2
              strbuf.append(syntaxEscape).append(syntaxSeparator2);
              i += syntaxSeparator2.length();
            } else {
              // no special treatment required
              strbuf.append(comp.charAt(i++));
            }
        start = false;
      }
    }
    return (strbuf.toString());
  }

  public String toString() {
    StringBuffer answer = new StringBuffer();
    String comp;
    boolean compsAllEmpty = true;
    int size = components.size();

    for (int i = 0; i < size; i++) {
      if (syntaxDirection == RIGHT_TO_LEFT) {
        comp =
            stringifyComp(components.elementAt(size - 1 - i));
      } else {
        comp = stringifyComp(components.elementAt(i));
      }
      if ((i != 0) && (syntaxSeparator != null)) {
        answer.append(syntaxSeparator);
      }
      if (comp.length() >= 1) {
        compsAllEmpty = false;
      }
      answer = answer.append(comp);
    }
    if (compsAllEmpty && (size >= 1) && (syntaxSeparator != null)) {
      answer = answer.append(syntaxSeparator);
    }
    return (answer.toString());
  }

  public boolean equals(Object obj) {
    if ((obj != null) && (obj instanceof NameImpl)) {
      NameImpl target = (NameImpl) obj;
      if (target.size() == this.size()) {
        Enumeration<String> mycomps = getAll();
        Enumeration<String> comps = target.getAll();
        while (mycomps.hasMoreElements()) {
          // %% comps could shrink in the middle.
          String my = mycomps.nextElement();
          String his = comps.nextElement();
          if (syntaxTrimBlanks) {
            my = my.trim();
            his = his.trim();
          }
          if (syntaxCaseInsensitive) {
            if (!(my.equalsIgnoreCase(his))) {
              return false;
            }
          } else {
            if (!(my.equals(his))) {
              return false;
            }
          }
        }
        return true;
      }
    }
    return false;
  }

  /**
   * Compares obj to this NameImpl to determine ordering.
   * Takes into account syntactic properties such as
   * elimination of blanks, case-ignore, etc, if relevant.
   *
   * Note: using syntax of this NameImpl and ignoring
   * that of comparison target.
   */
  public int compareTo(NameImpl obj) {
    if (this == obj) {
      return 0;
    }

    int len1 = size();
    int len2 = obj.size();
    int n = Math.min(len1, len2);

    int index1 = 0, index2 = 0;

    while (n-- != 0) {
      String comp1 = get(index1++);
      String comp2 = obj.get(index2++);

      // normalize according to syntax
      if (syntaxTrimBlanks) {
        comp1 = comp1.trim();
        comp2 = comp2.trim();
      }

      int local;
      if (syntaxCaseInsensitive) {
        local = comp1.compareToIgnoreCase(comp2);
      } else {
        local = comp1.compareTo(comp2);
      }

      if (local != 0) {
        return local;
      }
    }

    return len1 - len2;
  }

  public int size() {
    return (components.size());
  }

  public Enumeration<String> getAll() {
    return components.elements();
  }

  public String get(int posn) {
    return components.elementAt(posn);
  }

  public Enumeration<String> getPrefix(int posn) {
    if (posn < 0 || posn > size()) {
      throw new ArrayIndexOutOfBoundsException(posn);
    }
    return new NameImplEnumerator(components, 0, posn);
  }

  public Enumeration<String> getSuffix(int posn) {
    int cnt = size();
    if (posn < 0 || posn > cnt) {
      throw new ArrayIndexOutOfBoundsException(posn);
    }
    return new NameImplEnumerator(components, posn, cnt);
  }

  public boolean isEmpty() {
    return (components.isEmpty());
  }

  public boolean startsWith(int posn, Enumeration<String> prefix) {
    if (posn < 0 || posn > size()) {
      return false;
    }
    try {
      Enumeration<String> mycomps = getPrefix(posn);
      while (mycomps.hasMoreElements()) {
        String my = mycomps.nextElement();
        String his = prefix.nextElement();
        if (syntaxTrimBlanks) {
          my = my.trim();
          his = his.trim();
        }
        if (syntaxCaseInsensitive) {
          if (!(my.equalsIgnoreCase(his))) {
            return false;
          }
        } else {
          if (!(my.equals(his))) {
            return false;
          }
        }
      }
    } catch (NoSuchElementException e) {
      return false;
    }
    return true;
  }

  public boolean endsWith(int posn, Enumeration<String> suffix) {
    // posn is number of elements in suffix
    // startIndex is the starting position in this name
    // at which to start the comparison. It is calculated by
    // subtracting 'posn' from size()
    int startIndex = size() - posn;
    if (startIndex < 0 || startIndex > size()) {
      return false;
    }
    try {
      Enumeration<String> mycomps = getSuffix(startIndex);
      while (mycomps.hasMoreElements()) {
        String my = mycomps.nextElement();
        String his = suffix.nextElement();
        if (syntaxTrimBlanks) {
          my = my.trim();
          his = his.trim();
        }
        if (syntaxCaseInsensitive) {
          if (!(my.equalsIgnoreCase(his))) {
            return false;
          }
        } else {
          if (!(my.equals(his))) {
            return false;
          }
        }
      }
    } catch (NoSuchElementException e) {
      return false;
    }
    return true;
  }

  public boolean addAll(Enumeration<String> comps) throws InvalidNameException {
    boolean added = false;
    while (comps.hasMoreElements()) {
      try {
        String comp = comps.nextElement();
        if (size() > 0 && syntaxDirection == FLAT) {
          throw new InvalidNameException(
              "A flat name can only have a single component");
        }
        components.addElement(comp);
        added = true;
      } catch (NoSuchElementException e) {
        break;  // "comps" has shrunk.
      }
    }
    return added;
  }

  public boolean addAll(int posn, Enumeration<String> comps)
      throws InvalidNameException {
    boolean added = false;
    for (int i = posn; comps.hasMoreElements(); i++) {
      try {
        String comp = comps.nextElement();
        if (size() > 0 && syntaxDirection == FLAT) {
          throw new InvalidNameException(
              "A flat name can only have a single component");
        }
        components.insertElementAt(comp, i);
        added = true;
      } catch (NoSuchElementException e) {
        break;  // "comps" has shrunk.
      }
    }
    return added;
  }

  public void add(String comp) throws InvalidNameException {
    if (size() > 0 && syntaxDirection == FLAT) {
      throw new InvalidNameException(
          "A flat name can only have a single component");
    }
    components.addElement(comp);
  }

  public void add(int posn, String comp) throws InvalidNameException {
    if (size() > 0 && syntaxDirection == FLAT) {
      throw new InvalidNameException(
          "A flat name can only zero or one component");
    }
    components.insertElementAt(comp, posn);
  }

  public Object remove(int posn) {
    Object r = components.elementAt(posn);
    components.removeElementAt(posn);
    return r;
  }

  public int hashCode() {
    int hash = 0;
    for (Enumeration<String> e = getAll(); e.hasMoreElements(); ) {
      String comp = e.nextElement();
      if (syntaxTrimBlanks) {
        comp = comp.trim();
      }
      if (syntaxCaseInsensitive) {
        comp = comp.toLowerCase(Locale.ENGLISH);
      }

      hash += comp.hashCode();
    }
    return hash;
  }
}

final class NameImplEnumerator implements Enumeration<String> {

  Vector<String> vector;
  int count;
  int limit;

  NameImplEnumerator(Vector<String> v, int start, int lim) {
    vector = v;
    count = start;
    limit = lim;
  }

  public boolean hasMoreElements() {
    return count < limit;
  }

  public String nextElement() {
    if (count < limit) {
      return vector.elementAt(count++);
    }
    throw new NoSuchElementException("NameImplEnumerator");
  }
}
